﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace KegCommunication
{
    public enum PacketType : byte
    {
        Hello = KegPacket.KB_MESSAGE_TYPE_HELLO_ID,
        Thermometer = KegPacket.KB_MESSAGE_TYPE_THERMO_READING,
        FlowMeter = KegPacket.KB_MESSAGE_TYPE_METER_STATUS,
        OutputStatus = KegPacket.KB_MESSAGE_TYPE_OUTPUT_STATUS,
        Presence = KegPacket.KB_MESSAGE_TYPE_ONEWIRE_PRESENCE,
    };

    public class KegPacket
    {
        public byte type;

        public double meterTicksPerLiter = 6000;

        public const byte KB_MESSAGE_TYPE_HELLO_ID = 0x01;
        public const byte KB_MESSAGE_TYPE_HELLO_TAG_PROTOCOL_VERSION = 0x01;
        public const byte KB_MESSAGE_TYPE_HELLO_TAG_FIRMWARE_VERSION = 0x01;

        public const byte KB_MESSAGE_TYPE_THERMO_READING = 0x11;
        public const byte KB_MESSAGE_TYPE_THERMO_READING_TAG_SENSOR_NAME = 0x01;
        public const byte KB_MESSAGE_TYPE_THERMO_READING_TAG_SENSOR_READING = 0x02;

        public const byte KB_MESSAGE_TYPE_METER_STATUS = 0x10;
        public const byte KB_MESSAGE_TYPE_METER_STATUS_TAG_METER_NAME = 0x01;
        public const byte KB_MESSAGE_TYPE_METER_STATUS_TAG_METER_READING = 0x02;

        public const byte KB_MESSAGE_TYPE_OUTPUT_STATUS = 0x12;
        public const byte KB_MESSAGE_TYPE_OUTPUT_STATUS_TAG_OUTPUT_NAME = 0x01;
        public const byte KB_MESSAGE_TYPE_OUTPUT_STATUS_TAG_OUTPUT_READING = 0x02;

        public const byte KB_MESSAGE_TYPE_ONEWIRE_PRESENCE = 0x13;
        public const byte KB_MESSAGE_TYPE_ONEWIRE_PRESENCE_TAG_DEVICE_ID = 0x01;
        public const byte KB_MESSAGE_TYPE_ONEWIRE_PRESENCE_TAG_STATUS = 0x02;

        public const byte KB_MESSAGE_TYPE_PING = 0x81;

        public const byte KB_MESSAGE_TYPE_SET_OUTPUT = 0x83;
        public const byte KB_MESSAGE_TYPE_SET_OUTPUT_TAG_OUTPUT_ID = 0x01;
        public const byte KB_MESSAGE_TYPE_SET_OUTPUT_TAG_OUTPUT_MODE = 0x02;

        public const int KBSP_PAYLOAD_MAXLEN = 112;

        public static readonly byte[] KBSP_HEADER = { (byte)'K', (byte)'B', (byte)'S', (byte)'P', (byte)' ', (byte)'v', (byte)'1', (byte)':' };
        public static readonly byte[] KBSP_FOOTER = { (byte)'\r', (byte)'\n' };

        public override string ToString()
        {
            Type type = this.GetType();
            PropertyInfo[] properties = type.GetProperties();
            
            StringBuilder builder = new StringBuilder();
            builder.Append(type.Name);
            builder.Append(":");

            foreach (PropertyInfo pi in properties)
            {
                builder.Append(" ");
                builder.Append(pi.Name);
                builder.Append("=");
                builder.Append(pi.GetValue(this, null));
            }

            return builder.ToString();
        }
    }

    public class HelloPacket : KegPacket
    {
        private uint version;

        public uint Version
        {
            get { return version; }
        }

        public HelloPacket() {}

        public bool ParsePacket(byte[] buff)
        {
            bool PacketSuccess = false;

            if (KB_MESSAGE_TYPE_HELLO_ID == buff[0])
            {
                this.type = KB_MESSAGE_TYPE_HELLO_ID;
                short packetLength = BitConverter.ToInt16(buff, 1);

                if (KB_MESSAGE_TYPE_HELLO_TAG_FIRMWARE_VERSION == buff[4])
                {
                    this.version = BitConverter.ToUInt16(buff, 6);
                    PacketSuccess = true;
                }
            }
            return PacketSuccess;
        }
    }

    public class FlowMeterPacket : KegPacket
    {
        public int channel;
        public double ticks;

        public double Channel
        {
            get { return channel; }
        }

        public double Ticks
        {
            get { return ticks; }
        }

        public FlowMeterPacket()
        {
        }

        // TODO: Modify this into a packet.GetTag() call to match the original implementation
        public bool ParsePacket(byte[] buff)
        {
            bool PacketSuccess = false;

            if (KB_MESSAGE_TYPE_METER_STATUS == buff[0])
            {
                this.type = KB_MESSAGE_TYPE_METER_STATUS;
                short packetLength = BitConverter.ToInt16(buff, 1);

                if (KB_MESSAGE_TYPE_METER_STATUS_TAG_METER_NAME == buff[4])
                {
                    // Length of the meter name (always 5)
                    if (0x05 == buff[5])
                    {
                        // Next part should be "flow" or 66,6c,6f,77
                        if (buff[6] == 0x66 && buff[7] == 0x6c && buff[8] == 0x6f && buff[9] == 0x77)
                        {
                            this.channel = buff[10];
                            this.channel -= 0x30; // remove the default channel value

                            if (KB_MESSAGE_TYPE_METER_STATUS_TAG_METER_READING == buff[11])
                            {
                                // Next part is the reading itself
                                this.ticks = BitConverter.ToInt32(buff, 13);

                                PacketSuccess = true;
                            }
                        }
                    }
                }
            }

            return PacketSuccess;
        }

    }

    public class TemperaturePacket : KegPacket
    {
        public double value;
        public UInt64 address;
        public string addressString;

        public double Value
        {
            get { return value; }
        }

        public string Address
        {
            get { return addressString; }
        }

        public TemperaturePacket()
        {
        }

        // TODO: Modify this into a packet.GetTag() call to match the original implementation
        public bool ParsePacket(byte[] buff)
        {
            bool PacketSuccess = false;
            if (KB_MESSAGE_TYPE_THERMO_READING == buff[0])
            {
                this.type = KB_MESSAGE_TYPE_THERMO_READING;
                short packetLength = BitConverter.ToInt16(buff, 1);

                if (KB_MESSAGE_TYPE_THERMO_READING_TAG_SENSOR_NAME == buff[4])
                {
                    // Length of the sensor name (always 23)
                    if (0x17 == buff[5])
                    {
                        // Next part should be "thermo-" or 74 68 65 72 6D 6F 2D 
                        if (buff[6] == 0x74 &&
                            buff[7] == 0x68 &&
                            buff[8] == 0x65 &&
                            buff[9] == 0x72 &&
                            buff[10] == 0x6D &&
                            buff[11] == 0x6F &&
                            buff[12] == 0x2D)
                        {
                            // 64 bit address from the one-wire sensors.
                            this.address = BitConverter.ToUInt64(buff, 13);
                            byte[] add = new byte[16];
                            Array.Copy(buff, 13, add, 0, 16);
                            this.addressString = System.Text.ASCIIEncoding.ASCII.GetString(add);

                            //Console.WriteLine("Recieved address: " + this.addressString);

                            if (KB_MESSAGE_TYPE_THERMO_READING_TAG_SENSOR_READING == buff[29])
                            {
                                // Next part is the reading itself
                                byte length = buff[30];
                                //byte[] data = new byte[length];
                                //Array.Copy(buff, 13, data, 0, length);
                                //byte[] val = new byte[length];

                                Int32 temp = BitConverter.ToInt32(buff, 31);

                                // The value is stored as celius with precision
                                this.value = (temp / 1000000.0);

                                PacketSuccess = true;
                            }
                        }

                    }
                }
            }
            return PacketSuccess;
        }
    }
}
